package runnerworm.core;

import java.awt.*;
import java.awt.geom.*;


/**
 * <p>Title: Tunnel</p>
 * <p>Description: This will be where the worm will be running through.</p>
 * @author Guilherme Mauro Germoglio Barbosa
 */

public class Tunnel {

  /**
   * Here we have the upper wall.
   */
  private Polygon upperWall;
  /**
   * Here we have the bottom wall.
   */
  private Polygon bottomWall;
  /**
   * The obstacles the worm will have to pass.
   */
  private Polygon[] obstacles;
  /**
   * The rise coefiecient.
   */
  private final double RISE_COEFICIENT = 0.01127;
  /**
   * The Upper side.
   */
  private final int UPPER_SIDE = 1;
  /**
   * The bottom side.
   */
  private final int BOTTOM_SIDE = -1;
  /**
   * Upper height.
   */
  private final int UPPER_HEIGHT = 0;
  /**
   * Bottom height.
   */
  private final int BOTTOM_HEIGHT = 222;
  /**
   * Wall length.
   */
  private final int WALL_LENGTH = 8000;
  /**
   * If the tunnel is moving or not.
   */
  private boolean isMoving;
  /**
   * How much was already translated.
   */
  private int howMuchAlreadyTranslated;
  /**
   * If the tunnel has came to an end.
   */
  private boolean tunnelHasEnded;


  /**
   * Creates the tunnel and everything in it.
   */
  public Tunnel() {
    createWalls();
    createObstacles();
    this.isMoving = true;
    this.tunnelHasEnded = false;
  }

  /**
   * Creates the walls
   */
  private void createWalls() {
    upperWall = new Polygon();
    createOneWall(upperWall, UPPER_HEIGHT, WALL_LENGTH, UPPER_SIDE);

    bottomWall = new Polygon();
    createOneWall(bottomWall, BOTTOM_HEIGHT, WALL_LENGTH, BOTTOM_SIDE);
  }

  /**
   * Creates one wall, according to the coeficients given.
   * @param polygon The wall.
   * @param height The initial height coeficient.
   * @param length The wall length.
   * @param side For which side the wall is turned up.
   */
  private void createOneWall(Polygon polygon, int height, int length, int side) {
    polygon.addPoint(0, height);
    for (int k = 0; k < length; k = k + 50) {
      polygon.addPoint(k,(int)( height + side * ((RISE_COEFICIENT * k) + (Math.random() * 45) - 45)));
    }
    polygon.addPoint(length, height);
  }


  /**
   * Creates the obstacles.
   */
  private void createObstacles() {
    obstacles = new Polygon[153];
    for (int k = 0; k < 7650; k=k+50) {
      obstacles[k/50] = createOneObstacle(k+350);
    }
  }

  /**
   * Will create one obstacle according its X coordinate.
   * @param x The X coordinate.
   * @return The obstacle.
   */
  private Polygon createOneObstacle(int x) {
    Polygon obstacle = new Polygon();
    // Y coordinate
    int y = (int) ( (RISE_COEFICIENT * x) +
                   (Math.random() *
                    (BOTTOM_HEIGHT + (BOTTOM_SIDE * (RISE_COEFICIENT * x)) -
                     ( ( (RISE_COEFICIENT * x))))));
    // number of vertex
    int vertex = (int) (Math.random() * 4 + 4);
    // which direction
    int direction = (int) (Math.random() * 360);
    // create random vertex
    double[] vertexRadiuses = new double[vertex];
    for (int l = 0; l < vertex; l++) {
      vertexRadiuses[l] = (Math.random() * 5) + 10;
    }
    // create obstacles points
    for (int j = 0; j < vertex; j++) {
      obstacle.addPoint(
          (int) (x +
                 vertexRadiuses[j] *
                 Math.cos(direction + j * 2 * Math.PI / vertex)),
          (int) (y +
                 vertexRadiuses[j] *
                 Math.sin(direction + j * 2 * Math.PI / vertex))
          );
    }
    return obstacle;
  }

  /**
   * Checks a collision, given a polygon.
   * @param polygon A polygon, duh!
   * @return True if there was a collision, false case not.
   */
  public boolean checkCollision(Polygon polygon) {
    Rectangle2D rectangle = polygon.getBounds2D();
    return upperWall.intersects(rectangle) || bottomWall.intersects(rectangle) || checkObstaclesIntersection(polygon);
  }

  /**
   * Checks if the specified polygon collided to any obstacle.
   * @param polygon The polygon.
   * @return True if collided, false case not.
   */
  private boolean checkObstaclesIntersection(Polygon polygon) {
    int arrayEnd = (howMuchAlreadyTranslated/50) > obstacles.length ? obstacles.length : (howMuchAlreadyTranslated/50) ;
    int arrayBegining = (arrayEnd - 7) < 0 ? 0 : arrayEnd - 7;
    for (int k = arrayBegining; k < arrayEnd; k++) {
      if (PolygonIntersectionChecker.checkIntersection(obstacles[k],polygon))
        return true;
    }
    return false;
  }

  /**
   * Stops the tunnel moving (stops the thread related to the tunnel.)
   */
  public void stopTunnel() {
    this.isMoving = false;
  }

  /**
   * Translates all tunnel and its obstacles to the left.
   * @param howMuch How much the tunnel will be translated.
   */
  public synchronized void translate(int howMuch) {
    // translates the upper wall
    upperWall.translate(-howMuch, 0);
    // translates the bottom wall
    bottomWall.translate(-howMuch, 0);
    // translates the obstacles
    int arrayLength = obstacles.length;
    for (int k = 0; k < arrayLength; k++) {
      obstacles[k].translate(-howMuch, 0);
    }
    // stores how much translated
    howMuchAlreadyTranslated += howMuch;
    // checks if the tunnel has ended
    if (howMuchAlreadyTranslated > WALL_LENGTH)
      this.tunnelHasEnded = true;
  }

  /**
   * Checks if the tunnel is moving or not.
   * @return True if so, false case not.
   */
  public boolean isMoving() {
    return isMoving;
  }

  /**
   * Draws the tunnel.
   * @param g Where is to be drawn.
   */
  public synchronized void draw(Graphics g) {
    g.setColor(ColorTable.WALL_COLOR);
    drawWalls(g);
    g.setColor(ColorTable.OBSTACLE_COLOR);
    drawObstacles(g);
  }

  /**
   * Erases the tunnel.
   * @param g Where is to be erased.
   */
  public synchronized void erase(Graphics g) {
    g.setColor(ColorTable.BACKGROUND_COLOR);
    drawWalls(g);
    drawObstacles(g);
  }

  /**
   * Will draw the walls.
   * @param g Where is to be drawn.
   */
  private synchronized void drawWalls(Graphics g) {
    g.drawPolygon(upperWall);
    g.drawPolygon(bottomWall);
  }


  /**
   * Will draw the obstacles.
   * @param g Where is to be drawn.
   */
  private synchronized void drawObstacles(Graphics g) {
    int arrayEnd = (howMuchAlreadyTranslated/50) > obstacles.length ? obstacles.length : (howMuchAlreadyTranslated/50) ;
    int arrayBegining = (arrayEnd - 7) < 0 ? 0 : arrayEnd - 7;
    for (int k = arrayBegining; k < arrayEnd; k++) {
      g.drawPolygon(obstacles[k]);
    }
  }

  /**
   * Checks if the tunnel has came to an end.
   * @return True if it is the end, false case not.
   */
  public boolean hasEnded() {
    return tunnelHasEnded;
  }
}

